package com.dagit.network.traffic;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import com.dagit.adapter.logger.LogManager;
import com.dagit.adapter.logger.Logger;
import com.dagit.core.Configuration;
import com.dagit.core.pool.CommunicationException;
import com.dagit.network.Instance;
import com.dagit.network.LocalClientSocketPool;
import com.dagit.network.LocalServerSocket;

public class NetworkEngine {
	private static NetworkEngine manager = null;
	private static Lock instLock = new ReentrantLock();
	private static Logger logger = LogManager.getLogger(NetworkEngine.class.getName());
	//$$$$$$$$$$//
	
	private List<Instance> instances = new ArrayList<Instance>();
	
	private Map<String, LocalClientSocketPool> connectionPools = new ConcurrentHashMap<String, LocalClientSocketPool>();
	
	private NetworkEngine(){
		
	}
	public static List<Instance> getInstances(){
		return getInstance().instances;
	}
	protected static NetworkEngine getInstance(){
		if (manager != null){
			return manager;
		}
		try{
			instLock.lock();
			if (manager == null){
				manager = new NetworkEngine();
			}
		}finally{
			instLock.unlock();
		}
		return manager;			
		
	}
	protected static void registerInstance(Instance instance){
		getInstance().instances.add(instance);
	}
	public static void startEngine(Configuration c) throws IOException{
		LocalServerSocket.start(c.getLocalPort());
		getInstance();
		for(int i=0;i<c.getHosts().size();i++){
			String host = c.getHosts().get(i);
			int port = c.getPorts().get(i);
			if (!("127.0.0.1".equals(host) && c.getLocalPort() == port)){
				try {
					Socket socket = new Socket(host,port);
					Instance instance = new Instance(host, port);
					Message<Object> message = new Message<Object>();
					message.setClazz(Handshake.class.getName());
					message.setInput(instance);
					ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
					ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
					out.writeObject(message);
					while(in.available() < 0){
						
					}
					Boolean result = (Boolean)in.readObject();
					result.booleanValue();
					LocalClientSocketPool pool = new LocalClientSocketPool(instance);
					getInstance().connectionPools.put(host + ":" +port, pool);
				} catch (UnknownHostException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
				}
			}
		}
		
	}
	
	public static<E,T> T query(Instance instance,Message<E> message) {
		boolean _try = true;
		T response = null;
		while(_try){
			try{
				Socket socket = getInstance().connectionPools.get(instance.getIp() + ":" + instance.getPort()).checkOut();
				ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
				out.writeObject(message);
				ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
				response = (T) input.readObject();
				_try = false;
			}catch(Exception e){
				logger.error(e);
				_try = true;
			}
		}
		return response;
	}
	
	
	public static<E,T> List<T> collectResponses(Message<E> message) {
		List<T> responses = new ArrayList<T>();
		for (Instance instance : getInstance().instances){
			boolean _try = true;
			while(_try){
				try {
					T response = query(instance,message);
					responses.add(response);
				} catch (Exception e) {
					logger.error(e);
					_try = false;
				} 
			}
			
			
		}
		return responses;
	}
}
